/*
* Copyright 2014-2015 Daniel Pedraza-Arcega
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.grayfox.android.app.fragment;
import android.content.IntentSender;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.github.clans.fab.FloatingActionButton;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.grayfox.android.app.R;
import com.grayfox.android.app.dao.AccessTokenDao;
import com.grayfox.android.app.task.NetworkAsyncTask;
import com.grayfox.android.app.util.PicassoMarker;
import com.grayfox.android.app.widget.CategoryCursorAdapter;
import com.grayfox.android.client.PoisApi;
import com.grayfox.android.client.model.Category;
import com.grayfox.android.client.model.Poi;
import com.grayfox.android.client.model.Recommendation;
import com.squareup.picasso.Picasso;
import roboguice.fragment.RoboFragment;
import roboguice.inject.InjectView;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
public class ExploreFragment extends RoboFragment implements OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {
private static final int GOOGLE_API_CLIENT_CONNECTION_FAILURE_RESOLUTION_REQUEST = 5;
private static final String MAP_FRAGMENT_TAG = "MAP_FRAGMENT";
private static final String TAG = "ExploreFragment";
@InjectView(R.id.my_location_button) private FloatingActionButton myLocationButton;
@InjectView(R.id.progress_bar) private ProgressBar progressBar;
@InjectView(R.id.pager) private ViewPager viewPager;
@Inject private LocationManager locationManager;
@Inject private InputMethodManager inputMethodManager;
private boolean shouldRequestLocationUpdatesOnConnect;
private boolean shouldRestoreCurrentLocationInMap;
private boolean shouldRestoreRecommendationsInMap;
private boolean shouldRestorePois;
private MenuItem searchViewMenuItem;
private SearchView searchView;
private GoogleMap googleMap;
private GoogleApiClient googleApiClient;
private Location currentLocation;
private Recommendation[] recommendations;
private Poi[] pois;
private List<Marker> currentMarkers;
private RecommendationsTask recommendationsTask;
private CategoryFilteringTask categoryFilteringTask;
private PoisTask poisTask;
private CategoryCursorAdapter categoryAdapter;
public ExploreFragment() {
currentMarkers = new ArrayList<>();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_explore, menu);
categoryAdapter = new CategoryCursorAdapter(getActivity());
searchViewMenuItem = menu.findItem(R.id.action_search);
viewPager.setClipToPadding(false);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { }
@Override
public void onPageScrollStateChanged(int state) { }
@Override
public void onPageSelected(int position) {
Marker marker = currentMarkers.get(position);
marker.showInfoWindow();
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(marker.getPosition(), 15f));
}
});
searchView = (SearchView) searchViewMenuItem.getActionView();
searchView.setQueryHint(getString(R.string.search_for_places));
searchView.setSuggestionsAdapter(categoryAdapter);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
if (newText != null && !newText.trim().isEmpty() && newText.length() > 1) {
categoryFilteringTask = new CategoryFilteringTask().query(newText);
categoryFilteringTask.request();
}
return false;
}
});
searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
@Override
public boolean onSuggestionSelect(int position) {
return false;
}
@Override
public boolean onSuggestionClick(int position) {
onSuggestionClicked(position);
return false;
}
});
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_explore, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
FragmentManager fragmentManager = getChildFragmentManager();
SupportMapFragment fragment = (SupportMapFragment) fragmentManager.findFragmentByTag(MAP_FRAGMENT_TAG);
if (fragment == null) {
fragment = SupportMapFragment.newInstance();
fragmentManager.beginTransaction()
.replace(R.id.map_container, fragment, MAP_FRAGMENT_TAG)
.commit();
}
myLocationButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onRequestLocationUpdates();
}
});
googleApiClient = new GoogleApiClient.Builder(getActivity())
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
fragment.getMapAsync(this);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState == null) {
shouldRequestLocationUpdatesOnConnect = true;
shouldRestoreCurrentLocationInMap = false;
shouldRestoreRecommendationsInMap = false;
shouldRestorePois = false;
} else {
if (poisTask != null && poisTask.isActive()) onPreExecutePoisTask();
else if (pois != null) {
onPoisAcquired(pois);
onCompletePoisTaskTask();
}
if (recommendationsTask != null && recommendationsTask.isActive()) onPreExecuteRecommendationsTask();
else if (recommendations != null) {
onRecommendationsAcquired(recommendations);
onCompleteRecommendationsTask();
}
}
}
@Override
public void onStart() {
super.onStart();
googleApiClient.connect();
}
@Override
public void onStop() {
super.onStop();
stopLocationUpdates();
googleApiClient.disconnect();
PreferenceManager.getDefaultSharedPreferences(getActivity())
.edit()
.putFloat(getString(R.string.last_map_location_lat_key), (float) googleMap.getCameraPosition().target.latitude)
.putFloat(getString(R.string.last_map_location_lng_key), (float) googleMap.getCameraPosition().target.longitude)
.putFloat(getString(R.string.last_map_zoom_key), googleMap.getCameraPosition().zoom)
.commit();
}
@Override
public void onDestroy() {
super.onDestroy();
if (recommendationsTask != null && recommendationsTask.isActive()) recommendationsTask.cancel(true);
if (categoryFilteringTask != null && categoryFilteringTask.isActive()) categoryFilteringTask.cancel(true);
if (poisTask != null && poisTask.isActive()) poisTask.cancel(true);
}
@Override
public void onMapReady(GoogleMap googleMap) {
this.googleMap = googleMap;
googleMap.getUiSettings().setMapToolbarEnabled(false);
googleMap.getUiSettings().setMyLocationButtonEnabled(false);
LatLng latLng = new LatLng(
PreferenceManager.getDefaultSharedPreferences(getActivity()).getFloat(getString(R.string.last_map_location_lat_key), 0f),
PreferenceManager.getDefaultSharedPreferences(getActivity()).getFloat(getString(R.string.last_map_location_lng_key), 0f));
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng,
PreferenceManager.getDefaultSharedPreferences(getActivity()).getFloat(getString(R.string.last_map_zoom_key), 0f)));
googleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
@Override
public boolean onMarkerClick(Marker marker) {
int position = currentMarkers.indexOf(marker);
viewPager.setCurrentItem(position, true);
return false;
}
});
if (shouldRestoreCurrentLocationInMap) showCurrentLocationInMap();
if (shouldRestoreRecommendationsInMap) restoreRecommendationsInMap();
if (shouldRestorePois) restorePoisInMap();
}
private void onSuggestionClicked(int position) {
searchViewMenuItem.collapseActionView();
searchView.setQuery(null, false);
Category category = categoryAdapter.get(position);
poisTask = new PoisTask()
.currentLocation(getMapCenterLocation())
.radius(getRadiusFromMapProjection())
.categoryFoursquareId(category.getFoursquareId());
poisTask.request();
}
private void showCurrentLocationInMap() {
if (currentLocation != null) {
googleMap.addMarker(new MarkerOptions()
.position(new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude()))
.title(getString(R.string.current_location))
.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_map_location)));
}
}
private void restoreRecommendationsInMap() {
currentMarkers.clear();
if (recommendations.length > 0) googleMap.setPadding(0, 0, 0, (int) getResources().getDimension(R.dimen.recommendations_overlap));
for (Recommendation recommendation : recommendations) {
Marker marker = googleMap.addMarker(new MarkerOptions()
.position(new LatLng(recommendation.getPoi().getLocation().getLatitude(), recommendation.getPoi().getLocation().getLongitude()))
.title(recommendation.getPoi().getName()));
int backgroundResource = 0;
switch (recommendation.getType()) {
case GLOBAL:
backgroundResource = R.drawable.ic_map_pin_light_blue;
break;
case SELF:
backgroundResource = R.drawable.ic_map_pin_pink;
break;
case SOCIAL:
backgroundResource = R.drawable.ic_map_pin_dark_blue;
break;
}
Picasso.with(getActivity())
.load(recommendation.getPoi().getCategories()[0].getIconUrl())
.placeholder(R.drawable.ic_generic_category)
.into(new PicassoMarker(marker, backgroundResource, getActivity()));
currentMarkers.add(marker);
}
}
private void restorePoisInMap() {
currentMarkers.clear();
if (pois.length > 0) googleMap.setPadding(0, 0, 0, (int) getResources().getDimension(R.dimen.recommendations_overlap));
for (Poi poi : pois) {
Marker marker = googleMap.addMarker(new MarkerOptions()
.position(new LatLng(poi.getLocation().getLatitude(), poi.getLocation().getLongitude()))
.title(poi.getName()));
Picasso.with(getActivity())
.load(poi.getCategories()[0].getIconUrl())
.placeholder(R.drawable.ic_generic_category)
.into(new PicassoMarker(marker, R.drawable.ic_map_pin_light_blue, getActivity()));
currentMarkers.add(marker);
}
}
private void onPreLocationUpdate() {
currentLocation = null;
googleMap.setPadding(0, 0, 0, 0);
viewPager.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}
private boolean areAnyLocationProvidersEnabled() {
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
}
private void onRequestLocationUpdates() {
if (areAnyLocationProvidersEnabled()) {
shouldRequestLocationUpdatesOnConnect = true;
onPreLocationUpdate();
LocationRequest locationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(10 * 1_000) // 10 seconds
.setFastestInterval(1_000); // 1 second
LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, this);
} else {
Toast.makeText(getActivity().getApplicationContext(),
R.string.enable_location_updates,
Toast.LENGTH_LONG).show();
onCompleteLocationUpdate();
}
}
@Override
public void onLocationChanged(Location location) {
shouldRequestLocationUpdatesOnConnect = false;
shouldRestoreCurrentLocationInMap = true;
currentLocation = location;
stopLocationUpdates();
onCompleteLocationUpdate();
googleMap.clear();
showCurrentLocationInMap();
LatLng latLng = new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude());
float zoom = googleMap.getCameraPosition().zoom;
if (zoom < 12.5f) googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 12.5f));
else googleMap.moveCamera(CameraUpdateFactory.newLatLng(latLng));
recommendationsTask = new RecommendationsTask()
.location(location)
.radius(getRadiusFromMapProjection());
recommendationsTask.request();
}
private Location getMapCenterLocation() {
Location location = new Location(LocationManager.GPS_PROVIDER);
location.setLatitude(googleMap.getCameraPosition().target.latitude);
location.setLongitude(googleMap.getCameraPosition().target.longitude);
return location;
}
private int getRadiusFromMapProjection() {
LatLng point1 = googleMap.getProjection().getVisibleRegion().nearLeft;
LatLng point2 = googleMap.getProjection().getVisibleRegion().nearRight;
float[] results = new float[1];
Location.distanceBetween(point1.latitude, point1.longitude, point2.latitude, point2.longitude, results);
return Math.round(results[0] / 2f);
}
private void stopLocationUpdates() {
if (googleApiClient.isConnected()) LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, this);
}
private void onCompleteLocationUpdate() {
viewPager.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE);
}
private void onPreExecuteRecommendationsTask() {
viewPager.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}
private void onRecommendationsAcquired(Recommendation[] recommendations) {
shouldRestoreRecommendationsInMap = true;
shouldRestorePois = false;
this.recommendations = recommendations;
pois = null;
viewPager.setAdapter(new RecommentationPagerAdaper(recommendations, currentLocation));
googleMap.clear();
if (recommendations.length > 0) googleMap.setPadding(0, 0, 0, (int) getResources().getDimension(R.dimen.recommendations_overlap));
showCurrentLocationInMap();
currentMarkers.clear();
for (Recommendation recommendation : recommendations) {
Marker marker = googleMap.addMarker(new MarkerOptions()
.position(new LatLng(recommendation.getPoi().getLocation().getLatitude(), recommendation.getPoi().getLocation().getLongitude()))
.title(recommendation.getPoi().getName()));
int backgroundResource = 0;
switch (recommendation.getType()) {
case GLOBAL:
backgroundResource = R.drawable.ic_map_pin_light_blue;
break;
case SELF:
backgroundResource = R.drawable.ic_map_pin_pink;
break;
case SOCIAL:
backgroundResource = R.drawable.ic_map_pin_dark_blue;
break;
}
Picasso.with(getActivity())
.load(recommendation.getPoi().getCategories()[0].getIconUrl())
.placeholder(R.drawable.ic_generic_category)
.into(new PicassoMarker(marker, backgroundResource, getActivity()));
currentMarkers.add(marker);
}
}
private void onCompleteRecommendationsTask() {
viewPager.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
}
private void onAquireSuggestions(Category[] categories) {
if (categories != null) categoryAdapter.set(categories);
}
private void onPreExecutePoisTask() {
googleMap.setPadding(0, 0, 0, 0);
viewPager.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}
private void onPoisAcquired(Poi[] pois) {
shouldRestoreRecommendationsInMap = false;
shouldRestorePois = true;
this.pois = pois;
recommendations = null;
viewPager.setAdapter(new PoiPagerAdaper(pois, currentLocation));
googleMap.clear();
showCurrentLocationInMap();
currentMarkers.clear();
if (pois.length > 0) googleMap.setPadding(0, 0, 0, (int) getResources().getDimension(R.dimen.recommendations_overlap));
for (Poi poi : pois) {
Marker marker = googleMap.addMarker(new MarkerOptions()
.position(new LatLng(poi.getLocation().getLatitude(), poi.getLocation().getLongitude()))
.title(poi.getName()));
Picasso.with(getActivity())
.load(poi.getCategories()[0].getIconUrl())
.placeholder(R.drawable.ic_generic_category)
.into(new PicassoMarker(marker, R.drawable.ic_map_pin_light_blue, getActivity()));
currentMarkers.add(marker);
}
}
private void onCompletePoisTaskTask() {
viewPager.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
}
@Override
public void onConnected(Bundle bundle) {
Log.d(TAG, "Location services connected.");
if (shouldRequestLocationUpdatesOnConnect) onRequestLocationUpdates();
}
@Override
public void onConnectionSuspended(int cause) {
switch (cause) {
case CAUSE_SERVICE_DISCONNECTED:
Log.d(TAG, "Location services disconnected. Please reconnect.");
break;
case CAUSE_NETWORK_LOST:
Log.d(TAG, "Location services has lost connection. Please reconnect.");
break;
default: Log.d(TAG, "Location services suspended. Please reconnect.");
}
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
if (connectionResult.hasResolution()) {
try {
connectionResult.startResolutionForResult(getActivity(), GOOGLE_API_CLIENT_CONNECTION_FAILURE_RESOLUTION_REQUEST);
} catch (IntentSender.SendIntentException ex) {
Log.e(TAG, "Intent sender exception", ex);
}
} else {
Log.i(TAG, "Location services connection failed with code " + connectionResult.getErrorCode());
}
}
private class RecommentationPagerAdaper extends FragmentStatePagerAdapter {
private Recommendation[] recommendations;
private Location location;
public RecommentationPagerAdaper(Recommendation[] recommendations, Location location) {
super(getChildFragmentManager());
this.recommendations = recommendations;
this.location = location;
}
@Override
public RecommendationFragment getItem(int position) {
return RecommendationFragment.newInstance(recommendations[position], location);
}
@Override
public int getCount() {
return recommendations.length;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
// Do nothing here!
}
}
private class PoiPagerAdaper extends FragmentStatePagerAdapter {
private Poi[] pois;
private Location location;
public PoiPagerAdaper(Poi[] pois, Location location) {
super(getChildFragmentManager());
this.pois = pois;
this.location = location;
}
@Override
public PoiFragment getItem(int position) {
return PoiFragment.newInstance(pois[position], location);
}
@Override
public int getCount() {
return pois.length;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
// Do nothing here!
}
}
private class RecommendationsTask extends NetworkAsyncTask<Recommendation[]> {
@Inject private AccessTokenDao accessTokenDao;
@Inject private PoisApi poisApi;
private Location location;
private Integer radius;
private RecommendationsTask() {
super(getActivity().getApplicationContext());
}
private RecommendationsTask location(Location location) {
this.location = location;
return this;
}
private RecommendationsTask radius(int radius) {
this.radius = radius;
return this;
}
@Override
protected void onPreExecute() throws Exception {
super.onPreExecute();
onPreExecuteRecommendationsTask();
}
@Override
public Recommendation[] call() throws Exception {
com.grayfox.android.client.model.Location myLocation = new com.grayfox.android.client.model.Location();
myLocation.setLatitude(location.getLatitude());
myLocation.setLongitude(location.getLongitude());
return poisApi.awaitRecommendations(accessTokenDao.fetchAccessToken(), myLocation, radius);
}
@Override
protected void onSuccess(Recommendation[] recommendations) throws Exception {
super.onSuccess(recommendations);
onRecommendationsAcquired(recommendations);
}
@Override
protected void onFinally() throws RuntimeException {
super.onFinally();
onCompleteRecommendationsTask();
}
}
private class CategoryFilteringTask extends NetworkAsyncTask<Category[]> {
@Inject private PoisApi poisApi;
private String query;
private CategoryFilteringTask() {
super(getActivity().getApplicationContext());
}
public CategoryFilteringTask query(String query) {
this.query = query;
return this;
}
@Override
public Category[] call() throws Exception {
return poisApi.awaitCategoriesLikeName(query);
}
@Override
protected void onSuccess(Category[] categories) throws Exception {
super.onSuccess(categories);
onAquireSuggestions(categories);
}
}
private class PoisTask extends NetworkAsyncTask<Poi[]> {
@Inject private PoisApi poisApi;
private String categoryFoursquareId;
private Location currentLocation;
private int radius;
private PoisTask() {
super(getActivity().getApplicationContext());
}
public PoisTask currentLocation(Location currentLocation) {
this.currentLocation = currentLocation;
return this;
}
public PoisTask radius(int radius) {
this.radius = radius;
return this;
}
public PoisTask categoryFoursquareId(String categoryFoursquareId) {
this.categoryFoursquareId = categoryFoursquareId;
return this;
}
@Override
protected void onPreExecute() throws Exception {
super.onPreExecute();
onPreExecutePoisTask();
}
@Override
public Poi[] call() throws Exception {
com.grayfox.android.client.model.Location myLocation = new com.grayfox.android.client.model.Location();
myLocation.setLatitude(currentLocation.getLatitude());
myLocation.setLongitude(currentLocation.getLongitude());
return poisApi.awaitSearchByCategory(myLocation, radius, categoryFoursquareId);
}
@Override
protected void onSuccess(Poi[] pois) throws Exception {
super.onSuccess(pois);
onPoisAcquired(pois);
}
@Override
protected void onFinally() throws RuntimeException {
super.onFinally();
onCompletePoisTaskTask();
}
}
}